#include <iostream>
#include <sstream>
#include <cstring>
#include <cstdlib>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "boost/shared_ptr.hpp"
#include "boost/shared_array.hpp"

#include "WorkThread.h"
#include "HttpResponse.h"
#include "FileTypes.h"
#include "SafeQueueInstance.h"
#include "ClientConn.h"
#include "WordSegHandler.h"

using namespace std;
using namespace boost;

const uint32 BUF_SIZE = 2048;

WorkThread::WorkThread()
{
    _buf = new char[BUF_SIZE];

    _errmsg[0] = "OK";
    _errmsg[1] = "param [text] / [ie] missing";
    _errmsg[2] = "param [ie] must be " + ConfManager::Instance()->GetTextEncoding() + " (case-sensitive)";

    uint32 wordsegBufSize = ConfManager::Instance()->GetWordSegOutBufSize();
    int wordsegOutFlag = SCW_OUT_ALL | SCW_OUT_PROP;
    if ((_pout = scw_create_out(wordsegBufSize, wordsegOutFlag)) == NULL)
    {
        cerr << "Failed to create wordseg out buffer" << endl;
        exit(-1);
    }

    _tokens = new token_t[wordsegBufSize];
    _tokensSize = wordsegBufSize;
}

void* WorkThread::Process()
{
    while (true)
    {
        shared_ptr<ClientConn> conn(SafeQueueInstance::Instance()->Pop());
        uint32 connfd = conn.get()->GetConnFd();

        shared_ptr<HttpRequest> request(CreateHttpRequestFromConn(conn.get()));
        if (request == NULL)
        {
            close(connfd);
            continue;
        }

        HandleReq(connfd, request.get());
        close(connfd);
    }

    return NULL;
}

void WorkThread::HandleReq(uint32 fd, HttpRequest* request)
{
    if (request == NULL)
    {
        return;
    }

    map<string, string> params = request->GETParams().size() == 0
        ? request->POSTParams()
        : request->GETParams();

    HttpResponse response;
    response.SetHttpVersion(request->GetVersion());
    response.SetStatusAndReason("200", "OK");
    response.SetHeaderParam("Content-Type", "application/json");

    string ie = ConfManager::Instance()->GetTextEncoding();
    if (params.find("text") == params.end()
        || params.find("ie") == params.end()
        || params.find("ie")->second != ie)
    {
        int err =( params.find("text") == params.end()
                || params.find("ie") == params.end())
                 ? 1 : 2;
        string res = CreateResultJson(false, err);
        response.SetHeaderParam("Content-Length", ToString(res.size()));
        response.SetBodyAndLen(res.c_str(), res.size());
        WriteResponse(fd, response);

        return;
    }

    int wordsegFlag = SCW_CRF | SCW_DISAMB;
    if (scw_segment_words(WordSegHandler::Instance()->GetWordSegDicts(),
        _pout, params["text"].c_str(), params["text"].size(), LANGTYPE_SIMP_CHINESE,
        (void*)(&wordsegFlag)) == -1)
    {
        cerr << "Failed to segment " << params["text"] << endl;
        return;
    }

    string res = CreateResultJson(true, 0);
    response.SetHeaderParam("Content-Length", ToString(res.size()));
    response.SetBodyAndLen(res.c_str(), res.size());
    WriteResponse(fd, response);
}

HttpRequest* WorkThread::CreateHttpRequestFromConn(ClientConn* conn)
{
    uint32 fd = conn->GetConnFd();
    memset(_buf, 0, BUF_SIZE);

    int nread = 0;
    if ((nread = ReadHTTPHeader(fd, _buf, BUF_SIZE)) < 0)
    {
        return NULL;
    }

    HttpRequest* request = HttpRequest::CreateHttpRequest(_buf, nread);
    if (request == NULL)
    {
        return NULL;
    }
    request->SetClientIP(conn->GetIP());
    request->SetClientPort(conn->GetPort());

    map<string, string> params = request->GetHeadParams();
    if (params.find("Content-Length") == params.end())
    {
        // TODO: Will check if "Content-Length" is necessary
        // for HEAD, CONNECT an so on, and add them.
        if (request->GetMethod() == GET)
        {
            return request;
        }
        HttpResponse response;
        response.SetHttpVersion(request->GetVersion());
        response.SetStatusAndReason("411", "Length Required");
        response.SetHeaderParam("Content-Length", "0");
        WriteResponse(fd, response);

        return NULL;
    }

    int postAllLen = atoi(params["Content-Length"].c_str());
    if (postAllLen == 0)
    {
        return request;
    }

    uint32 postReadLen = request->GetPostDataLen();
    const char* postDataRead = request->GetPostData();
    int postLeftLen = postAllLen - postReadLen;

    if (postLeftLen > 0)
    {
        // Make postdata end with '\0', be easy to parse post params
        char* postData = CreateCStr(postAllLen + 1);
        if (postReadLen > 0)
        {
            strncpy(postData, postDataRead, postReadLen);
            delete [] postDataRead;
        }

        // When Content-Length is bigger than real post body length,
        // it will cause WorkThread forever-wait until client close connection
        readn(fd, postData + postReadLen, postLeftLen);
        request->SetPostData(postData);
        request->SetPostDataLen(postAllLen);
    }

    map<string, string> post = GetParams(string(request->GetPostData()));
    request->SetPOST(post);

    return request;
}

string WorkThread::CreateResultJson(bool success, int err)
{
    stringstream s;
    s << "{success: " << (success ? "true" : "false")
      << ", errno: " << err
      << ", errmsg: \"" << _errmsg[err] << "\""
      << ", result: [";

    if (err == 0)
    {
        int count = scw_get_token_1(_pout, SCW_OUT_WPCOMP, _tokens, _tokensSize);
        s << "\"WPCOMP\": [";
        for (int i = 0; i < count; i++)
        {
            s << "[";
            s << "text: \"" << string(_tokens[i].buffer) << "\",";
            s << "offset: " << _tokens[i].offset << ",";
            s << "],";
        }
        s << "],";

        count = scw_get_token_1(_pout, SCW_OUT_BASIC, _tokens, _tokensSize);
        s << "\"BASIC\": [";
        for (int i = 0; i < count; i++)
        {
            s << "[";
            s << "text: \"" << string(_tokens[i].buffer) << "\",";
            s << "offset: " << _tokens[i].offset << ",";
            s << "],";
        }
        s << "],";

        count = scw_get_token_1(_pout, SCW_OUT_SUBPH, _tokens, _tokensSize);
        s << "\"SUBPH\": [";
        for (int i = 0; i < count; i++)
        {
            s << "[";
            s << "text: \"" << string(_tokens[i].buffer) << "\",";
            s << "offset: " << _tokens[i].offset << ",";
            s << "],";
        }
        s << "],";

        count = scw_get_token_1(_pout, SCW_OUT_NEWWORD, _tokens, _tokensSize);
        s << "\"NEWWORD\": [";
        for (int i = 0; i < count; i++)
        {
            s << "[";
            s << "text: \"" << string(_tokens[i].buffer) << "\",";
            s << "offset: " << _tokens[i].offset << ",";
            s << "],";
        }
        s << "],";

        count = scw_get_token_1(_pout, SCW_OUT_HUMANNAME, _tokens, _tokensSize);
        s << "\"HUMANNAME\": [";
        for (int i = 0; i < count; i++)
        {
            s << "[";
            s << "text: \"" << string(_tokens[i].buffer) << "\",";
            s << "offset: " << _tokens[i].offset << ",";
            s << "],";
        }
        s << "],";

        count = scw_get_token_1(_pout, SCW_OUT_BOOKNAME, _tokens, _tokensSize);
        s << "\"BOOKNAME\": [";
        for (int i = 0; i < count; i++)
        {
            s << "[";
            s << "text: \"" << string(_tokens[i].buffer) << "\",";
            s << "offset: " << _tokens[i].offset << ",";
            s << "],";
        }
        s << "],";

        count = scw_get_token_1(_pout, SCW_OUT_DISAMB, _tokens, _tokensSize);
        s << "\"DISAMB\": [";
        for (int i = 0; i < count; i++)
        {
            s << "[";
            s << "text: \"" << string(_tokens[i].buffer) << "\",";
            s << "offset: " << _tokens[i].offset << ",";
            s << "],";
        }
        s << "],";
    }

    s << "]}";

    return s.str();
}

WorkThread::~WorkThread()
{
    delete [] _buf;
    delete [] _tokens;
    delete _pout;
}
